Vài tuần trước, tôi quyết định xây dựng một con bot của riêng mình cho Polymarket. Bản đầy đủ đã mất vài tuần của tôi.
Tôi sẵn lòng bỏ công sức vào việc này vì thực sự có lỗ hổng hiệu suất trên Polymarket, mặc dù đã có một số con bot trên thị trường đang tận dụng lỗ hổng hiệu suất này, nhưng vẫn chưa đủ, cơ hội trên thị trường này vẫn nhiều hơn số lượng bot.
Logic của con bot này dựa trên một tập hợp chiến lược mà tôi đã thực hiện thủ công trong quá khứ, và để tăng hiệu suất, tôi đã tự động hóa nó. Con bot này chạy trên thị trường "BTC 15 Phút Tăng / Giảm (BTC 15 Phút UP/DOWN)".

Bot chạy một chương trình giám sát thời gian thực, có thể tự động chuyển sang vòng lặp 15 phút BTC hiện tại, thông qua luồng WebSocket truyền trực tiếp giá mua tốt nhất / giá bán tốt nhất (best bid/ask), hiển thị một UI cố định trên terminal và cho phép điều khiển toàn diện thông qua lệnh văn bản.

Trong chế độ thủ công, bạn có thể đặt lệnh trực tiếp.
mua lên <usd> / mua xuống <usd>: Mua một số tiền USD cụ thể.
mua cổ phiếu lên <số cổ phần> / mua cổ phiếu xuống <số cổ phần>: Mua một số lượng cổ phần cụ thể, sử dụng lệnh GIỚI HẠN (LIMIT) + GTC (Hủy trước khi hết hạn) thân thiện với người dùng, được giao dịch với giá bán tốt nhất hiện tại (best ask).
Chế độ tự động chạy một vòng lặp hai chân.
Bước đầu tiên, nó chỉ quan sát biến động giá trong windowMin phút sau mỗi vòng. Nếu bất kỳ bên nào giảm đủ nhanh (giảm ít nhất movePct trong khoảng 3 giây), nó sẽ kích hoạt "Chân 1" và mua vào bên giảm giá mạnh.
Sau khi hoàn thành Chặng 1, robot sẽ không bao giờ mua vào cùng một phía lần nữa. Nó sẽ đợi "Chặng 2 (Chặn 2, nghĩa là lướt sóng)" và chỉ kích hoạt khi điều kiện sau được thỏa mãn: leg1EntryPrice + oppositeAsk <= sumTarget.
Khi điều kiện này được thỏa mãn, nó sẽ mua vào phía ngược lại. Sau khi hoàn thành Chặng 2, vòng lặp kết thúc và robot quay trở lại trạng thái quan sát, đợi tín hiệu sụt giảm tiếp theo sử dụng cùng các thông số.
Nếu có sự thay đổi vòng lặp trong quá trình này, robot sẽ hủy bỏ vòng lặp đang mở và bắt đầu lại với cùng cấu hình trong vòng lặp tiếp theo.
Cài đặt tham số tự động như sau: auto on <số cổ phiếu> [sum=0.95] [move=0.15] [windowMin=2]
· số cổ phiếu: Kích thước vị thế sử dụng cho hai chặng giao dịch.
· sum: Ngưỡng cho phép lướt sóng.
· move (movePct): Ngưỡng sụt giảm (ví dụ, 0.15 = 15%).
· windowMin: Thời lượng cho phép thực hiện Chặng 1 tính từ mỗi vòng lặp.
Logic của robot rất đơn giản: đợi sự sụt giảm mạnh, mua vào phía vừa sụt giảm xong, sau đó đợi giá ổn định và lướt sóng bằng cách mua vào phía ngược, đồng thời đảm bảo: priceUP + priceDOWN < 1.
Nhưng logic này cần được kiểm tra. Liệu nó có thực sự hiệu quả trong dài hạn không? Quan trọng hơn, robot có nhiều tham số (số cổ phiếu, tổng, phần trăm di chuyển, số phút cửa sổ, v.v.). Bộ tham số nào là tối ưu nhất và có thể làm tăng lợi nhuận?
Ý tưởng đầu tiên của tôi là để robot chạy thực tế trong một tuần và quan sát kết quả. Vấn đề là quá trình này mất quá nhiều thời gian và chỉ có thể kiểm tra một bộ tham số, trong khi tôi cần kiểm tra nhiều bộ tham số.
Ý tưởng thứ hai của tôi là sử dụng dữ liệu lịch sử trực tuyến từ API CLOB của Polymarket để thực hiện kiểm tra lại. Thật không may, đối với thị trường tăng/giảm BTC mỗi 15 phút, điểm cuối dữ liệu lịch sử luôn trả về tập dữ liệu trống. Không có những lần biến động giá lịch sử (tick), việc kiểm tra lại tương tương không thể phát hiện sự sụt giảm "xấp xỉ trong khoảng 3 giây", không thể kích hoạt Chặng 1, và bất kỳ bộ tham số nào cũng sẽ tạo ra 0 vòng lặp và 0% tỷ suất sinh lời đầu tư (ROI).

Sau khi tiến hành điều tra sâu hơn, tôi nhận thấy người dùng khác cũng gặp vấn đề tương tự khi truy cập dữ liệu lịch sử của một số thị trường. Tôi đã thử nghiệm các thị trường khác thực sự trả về dữ liệu lịch sử và kết luận rằng: đối với thị trường cụ thể này, dữ liệu lịch sử hoàn toàn không được lưu giữ.
Do hạn chế này, cách duy nhất để thử nghiệm lại chiến lược đó một cách đáng tin cậy là trong quá trình chạy robot, tôi sẽ tạo ra tập dữ liệu lịch sử của riêng mình bằng cách ghi lại giá mua tốt nhất hiện thời (best-ask) thời gian thực.

Bộ ghi sẽ ghi trạng thái hiện tại vào đĩa, bao gồm các thông tin sau:
· Dấu thời gian
· Nhận dạng chu kỳ (round slug)
· Thời gian còn lại (tính bằng giây)
· ID mã thông báo UP/DOWN
· Giá mua tốt nhất UP/DOWN
Sau đó, "Thử nghiệm lại đã ghi" sẽ phát lại các trạng thái này và áp dụng logic tự động tương tự một cách xác định. Điều này đảm bảo rằng tôi có thể tiếp cận dữ liệu tần suất cao cần thiết để phát hiện tình trạng bán ra giảm mạnh và cơ hội đảo chiều.
Tôi đã tổng cộng thu thập được 6 GB dữ liệu trong vòng 4 ngày. Tôi có thể ghi lại nhiều hơn nhưng tôi cho rằng đây là đủ để thử nghiệm các bộ tham số khác nhau.

Tôi đã bắt đầu thử nghiệm bộ tham số này:
· Số dư ban đầu: $1,000
· Mỗi lần giao dịch 20 cổ phiếu
· sumTarget = 0.95
· Ngưỡng giảm mạnh = 15%
· windowMin = 2 phút
Tôi cũng áp dụng mức phí cố định 0.5% và lệch giá 2% để duy trì trong tình huống thận trọng.
Thử nghiệm lại cho thấy ROI là 86%, chỉ trong vài ngày $1,000 đã biến thành $1,869.

Sau đó, tôi đã thử nghiệm bộ tham số mạnh hơn:
· Số dư ban đầu: $1,000
· Mỗi lần giao dịch 20 cổ phiếu
· sumTarget = 0.6
· Ngưỡng giảm mạnh = 1%
· windowMin = 15 phút
Kết quả: Sau 2 ngày, tỷ lệ sinh lời đầu tư là -50%.

Điều này rõ ràng cho thấy việc lựa chọn tham số là yếu tố quan trọng nhất. Nó có thể giúp bạn kiếm được một khoản tiền lớn, nhưng cũng có thể dẫn đến thiệt hại nặng nề.
Mặc dù đã bao gồm phí và chênh lệch giá, việc Backtesting vẫn có hạn chế của nó.
· Đầu tiên, nó chỉ sử dụng dữ liệu trong vài ngày, điều này có thể không đủ để có cái nhìn toàn diện về thị trường.
· Nó phụ thuộc vào bản ghi của giá bán tối ưu; trong thực tế, các lệnh có thể được giao dịch một phần hoặc với giá khác nhau. Ngoài ra, sâu độ của sổ đặt hàng và khối lượng giao dịch có thể không được mô hình hóa.
· Chúng không bắt kịp các biến động dưới mức giây (dữ liệu được lấy mẫu mỗi giây). Mặc dù Backtesting có dấu thời gian 1 giây, nhưng có thể có rất nhiều biến động xảy ra giữa các giây.
· Trong quá trình Backtesting, slippage là không đổi, không có mô phỏng độ trễ thay đổi (ví dụ 200–1500 mili giây) hoặc công suất mạng.
· Mỗi giao dịch được xem như thực hiện "ngay lập tức" (không có xếp hàng lệnh, không có lệnh chờ).
· Phí được thu theo cách thống nhất, trong khi trong thực tế, phí có thể phụ thuộc vào: thị trường / token, người đặt lệnh và người ăn lệnh, cấp độ phí hoặc điều kiện.
Để duy trì tính bi quan, tôi áp dụng một quy tắc: Nếu Leg 2 không được thực hiện trước khi thị trường đóng cửa, Leg 1 sẽ bị xem là tổn thất toàn bộ.
Điều này là cố ý thận trọng, nhưng không luôn phản ánh thực tế:
· Đôi khi Leg 1 có thể được đóng trước,
· Đôi khi nó cuối cùng ở trong tiền (ITM) và chiến thắng,
· Đôi khi thua lỗ có thể chỉ là một phần chứ không phải toàn bộ.
Mặc dù việc thua lỗ có thể được định giá cao, nhưng điều này cung cấp một kịch bản "tệ nhất" thực tế.
Quan trọng nhất, Backtesting không thể mô phỏng tác động của lệnh lớn của bạn đối với sổ đặt hàng hoặc hành vi săn lùng của các bên giao dịch khác. Trong thực tế, lệnh của bạn có thể:
· Làm xáo trộn sổ đặt hàng,
· Thu hút hoặc đẩy ra các bên giao dịch khác,
· Dẫn đến slippage phi tuyến tính.
Backtesting giả định rằng bạn chỉ là người khai thác thanh khoản thuần túy (price taker), không có ảnh hưởng nào.
Cuối cùng, nó không mô phỏng các hạn chế tần suất (rate limits), lỗi API, lệnh bị từ chối, tạm dừng, vượt quá thời gian chờ, kết nối lại, hoặc trường hợp robot bận và bỏ lỡ tín hiệu.
Backtesting là một phương tiện rất hữu ích để xác định phạm vi tham số tốt, nhưng nó không phải là đảm bảo 100% vì một số hiệu ứng trong thế giới thực không thể được mô hình hóa.
Tôi dự định chạy robot trên Raspberry Pi để tránh tiêu tốn tài nguyên máy chủ chính của tôi và duy trì việc vận hành liên tục 24/7.
Nhưng vẫn còn không gian cải tiến đáng kể:
· Sử dụng Rust thay vì JavaScript sẽ cung cấp hiệu suất và thời gian xử lý xuất sắc hơn.
· Chạy nút RPC Polygon chuyên dụng sẽ giảm thêm độ trễ.
· Triển khai trên một VPS gần máy chủ Polymarket cũng sẽ giảm đáng kể độ trễ.
Chắc chắn còn nhiều cách tối ưu khác mà tôi chưa khám phá. Hiện tại, tôi đang học Rust vì nó đang trở thành ngôn ngữ không thể thiếu trong phát triển Web3.
Liên kết gốc